package br.com.tcc.systemdx.service.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Random;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import br.com.tcc.systemdx.persistence.OcorrenciaDoencaDAO;
import br.com.tcc.systemdx.persistence.TemperaturaDiariaDAO;
import br.com.tcc.systemdx.service.MecanismoMineracaoService;

@Service
@Scope("singleton")
@Transactional
public class MecanismoMineracaoServiceImpl implements MecanismoMineracaoService, Serializable {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = -718245601603134809L;

	@Autowired
	private TemperaturaDiariaDAO temperaturaDiariaDAO;
	
	@Autowired
	private OcorrenciaDoencaDAO ocorrenciaDoencaDAO;
	
	public ResultadoMineracao resultado;
	
	
	
	public void acionarMineracaoTemperaturaOcorrencias(String cid) {
		int folding = 10;
		List<TemperaturaDiscretizadaOcorrenciaMes> dadosCompletos = tratamentoTemperaturas();
		dadosCompletos = incluirOcorrencias(dadosCompletos, cid);
		
		int alta = 0, media = 0, baixa = 0;
		for(int i = 0; i < dadosCompletos.size(); ++i) {
			if(dadosCompletos.get(i).temp.equalsIgnoreCase("alta")) alta++;
			if(dadosCompletos.get(i).temp.equalsIgnoreCase("baixa")) media++;
			if(dadosCompletos.get(i).temp.equalsIgnoreCase("media")) baixa++;
		}
		
		System.out.println("Alta: "+ alta +" Baixa:" + baixa+" Media: "+media);
		
		List<TemperaturaDiscretizadaOcorrenciaMes>[] dezPartes = new List[folding];
		
		Random randomGenerator = new Random();
		int umaParte = dadosCompletos.size()/folding;
		
		for(int i = 0; i < folding; ++i) {
			dezPartes[i] = new ArrayList<TemperaturaDiscretizadaOcorrenciaMes>();
			for(int j = 0; j < umaParte; ++j) {
				int index = randomGenerator.nextInt(dadosCompletos.size());
				dezPartes[i].add(dadosCompletos.remove(index));
			}
		}
		
		
		
		LimitesOcorrencias lo = new LimitesOcorrencias();
		List<String> printRegras[] = new List[folding];
		int numeroRegras[] = new int[folding];
		double erros[] = new double[folding];
		
		for(int i = 0; i < folding; ++i) {
			List<TemperaturaDiscretizadaOcorrenciaMes> novePartes = new ArrayList<TemperaturaDiscretizadaOcorrenciaMes>();
			for(int j = 0; j < dezPartes.length; ++j) {
				if(j != i) {
					novePartes.addAll(dezPartes[i]);
				}
			}
			novePartes = ordenarPorOcorrencias(novePartes);
			lo.definirLimitesNumericosOcorrencias(novePartes);
			printRegras[i] = lo.printLimites;
			erros[i] = testarDados(lo, dezPartes[i]);
			numeroRegras[i] = lo.limites.size();
		}
		
		int interros[] = new int[folding];
		for(int i = 0; i < folding; ++i) {
			System.out.println(erros[i]*100);
			interros[i] = (int) (erros[i]*100);
		}
		
		this.resultado = new ResultadoMineracao(interros, numeroRegras);
		this.resultado.setPrintRegras(printRegras);
		this.resultado.setErros(erros);
		resultado.imprimirConsole();
	}
	
	private float testarDados(LimitesOcorrencias lo,
						List<TemperaturaDiscretizadaOcorrenciaMes> umaParte) {
		float count = 0;
		for(int i = 0; i < umaParte.size(); ++i) {
			if(lo.testarDado(umaParte.get(i).temp, umaParte.get(i).numeroOcorrencias))
				count += 1;
		}
		return count/umaParte.size();
	}
	
	private List<TemperaturaDiscretizadaOcorrenciaMes> ordenarPorOcorrencias(
												List<TemperaturaDiscretizadaOcorrenciaMes> dados) {
		List<TemperaturaDiscretizadaOcorrenciaMes> dadosOrdenados
									= new ArrayList<TemperaturaDiscretizadaOcorrenciaMes>();

		
		for(int i = dados.size()-1; i >= 0; --i) {
			int ocorrenciaMin = Integer.MAX_VALUE;
			int indexMin = -1;
			for(int j = 0; j < dados.size(); ++j) {
				if(dados.get(j).getNumeroOcorrencias() < ocorrenciaMin){
					ocorrenciaMin = dados.get(j).getNumeroOcorrencias();
					indexMin = j;
				}
			}
			dadosOrdenados.add(dados.get(indexMin));
			dados.remove(indexMin);
		}
		
		for(int i = 0; i < dadosOrdenados.size(); ++i) {
			if(dadosOrdenados.get(i).numeroOcorrencias == 0) {
				dadosOrdenados.remove(i);
				i -=1;
			}
		}
		return dadosOrdenados;
	}
	
	private List<TemperaturaDiscretizadaOcorrenciaMes> tratamentoTemperaturas() {
		List<TemperaturaDiscretizadaOcorrenciaMes> temperaturasDiscretizadas = 
									new ArrayList<TemperaturaDiscretizadaOcorrenciaMes>();
		for(int year = 1999; year <= 2004; ++year) {
			for(int month = 0; month < 12; ++month) {
				Calendar calendar = Calendar.getInstance();
				calendar.set(year, month, 1, 0, 0, 0);
				
				double teste = temperaturaDiariaDAO.getAvarageByMonthYear(month, year);
				temperaturasDiscretizadas.add(new TemperaturaDiscretizadaOcorrenciaMes(
								teste, 
								calendar.getTime()));
			}
		}
		return temperaturasDiscretizadas;
	}
	
	private List<TemperaturaDiscretizadaOcorrenciaMes> incluirOcorrencias(
									List<TemperaturaDiscretizadaOcorrenciaMes> itens, String cid) {
		for(int i = 0; i < itens.size(); ++i) {
			Calendar calendar = Calendar.getInstance();
			calendar.setTime(itens.get(i).getDate());
			
			int ocorrencias = ocorrenciaDoencaDAO.countByMonthYear(
									calendar.get(Calendar.MONTH), 
									calendar.get(Calendar.YEAR), 
									cid);
			itens.get(i).setNumeroOcorrencias(ocorrencias);
		}
		return itens;
	}
	
	private void imprimirDados(List<TemperaturaDiscretizadaOcorrenciaMes> dados){
		for(int i = 0; i < dados.size(); ++i) {
			System.out.println(dados.get(i).toString());
		}
	}
	
	private class TemperaturaDiscretizadaOcorrenciaMes {
	
		private String temp;
		private Date date;
		private int numeroOcorrencias;
		
		public TemperaturaDiscretizadaOcorrenciaMes(double temp, Date date) {
			if(temp > 24) {
				this.temp = "alta";
			} else if(temp < 18) {
				this.temp = "baixa";
			} else {
				this.temp = "media";
			}
			
			this.date = date;
		}
		
		public String toString(){
			return "temp: " + temp + " ocorrencias: " + numeroOcorrencias;
		}

		public String getTemp() {
			return temp;
		}
		
		public void setTemp(String temp) {
			this.temp = temp;
		}

		public Date getDate() {
			return date;
		}
		
		public void setDate(Date date) {
			this.date = date;
		}

		public int getNumeroOcorrencias() {
			return numeroOcorrencias;
		}

		public void setNumeroOcorrencias(int numeroOcorrencias) {
			this.numeroOcorrencias = numeroOcorrencias;
		}

	}
	
	private class LimitesOcorrencias {
		private class LimiteOcorrencia {
			int tipoLimite;
			String classeRelacionada;
			int limiteSuperior, limiteInferior;
			
			int ENTRE = 1;
			int MENORQ = 0;
			int MAIORQ = 2;
			
			public LimiteOcorrencia(String classeRelacionada, int limiteSuperior, int limiteInferior) {
				this.tipoLimite = ENTRE;
				this.limiteInferior = limiteInferior;
				this.limiteSuperior = limiteSuperior;
				this.classeRelacionada = classeRelacionada;
			}
			
			public LimiteOcorrencia(String classeRelacionada, int limiteSuperior) {
				this.tipoLimite = MAIORQ;
				this.limiteSuperior = limiteSuperior;
				this.classeRelacionada = classeRelacionada;
			}
			
			public LimiteOcorrencia(int limiteInferior, String classeRelacionada) {
				this.tipoLimite = MENORQ;
				this.limiteInferior = limiteInferior;
				this.classeRelacionada = classeRelacionada;
			}
			
		}
		
		private List<LimiteOcorrencia> limites;
		
		public  List<String> printLimites;
		
		public boolean testarDado(String classe, int ocorrencia) {
			for(int i=0; i < limites.size(); ++i) {
				if(limites.get(i).tipoLimite == 0 && ocorrencia < limites.get(i).limiteInferior) {
					if(classe.equalsIgnoreCase(limites.get(i).classeRelacionada)) return true;
					else return false;
				}
				else if(limites.get(i).tipoLimite == 1 && ocorrencia > limites.get(i).limiteInferior &&
													ocorrencia < limites.get(i).limiteSuperior) {
					if(classe.equalsIgnoreCase(limites.get(i).classeRelacionada)) return true;
					else return false;
				} else if(limites.get(i).tipoLimite == 2 && ocorrencia > limites.get(i).limiteSuperior) {
					if(classe.equalsIgnoreCase(limites.get(i).classeRelacionada)) return true;
					else return false;
				}
			}
			return false;
		}

		public void definirLimitesNumericosOcorrencias(
									List<TemperaturaDiscretizadaOcorrenciaMes> dadosOrdenados) {
			int frequenciaParaIgnorar = dadosOrdenados.size()/50+1;
			
			List<LimiteOcorrencia> l = new ArrayList<LimiteOcorrencia>();
			
			int acertos = 0, erros = 0;
			boolean reset = false;
			String anterior = "", hipotese = "", atual = "";
			int inferior = -1, superior;
			for(int i = 0; i < dadosOrdenados.size(); ++i) {
				
				if(hipotese.isEmpty()) {
					hipotese = dadosOrdenados.get(i).temp;
					acertos = 0; erros = 0;
				}
				atual = dadosOrdenados.get(i).temp;
				
				if(hipotese.equals(atual)) ++acertos;
				else ++erros;
				
				if(erros > frequenciaParaIgnorar) {
					for(int j = 0; j <= erros; ++j) {
						if(dadosOrdenados.get(i-j).getTemp().equals(hipotese)) {
							superior = dadosOrdenados.get(i - j).getNumeroOcorrencias();
							if(i-j+1 != dadosOrdenados.size()) {
								superior = (superior + dadosOrdenados.get(i - j + 1).getNumeroOcorrencias()) /2;
							}
							if(inferior == -1) {
								l.add(new LimiteOcorrencia(superior, hipotese));
								inferior = superior;
							} else {
								l.add(new LimiteOcorrencia(hipotese, superior, inferior));
								inferior = superior;
							}
							hipotese = "";
							break;
						}
					}
				}
				
				if(i == dadosOrdenados.size() - 1 && inferior != -1) {
					l.add(new LimiteOcorrencia(hipotese, inferior));
				} else if(i == dadosOrdenados.size() - 1) {
					l.add(new LimiteOcorrencia(hipotese, inferior));
					l.add(new LimiteOcorrencia(inferior, hipotese));
				}
				
				
			}
			limites = l;
			
			printLimites = new ArrayList<String>();
			
			for(int i = 0; i < limites.size(); ++i) {
				if(i == 0) {
					System.out.print(limites.get(i).classeRelacionada + 	
												" | " + limites.get(i).limiteInferior);
					printLimites.add(limites.get(i).classeRelacionada + 	
					" < " + limites.get(i).limiteInferior);
				}
				else if(i != limites.size() -1) {
					System.out.print(" | " + limites.get(i).classeRelacionada + " | " + 
												limites.get(i).limiteSuperior);
					printLimites.add(limites.get(i).limiteInferior + " < " + limites.get(i).classeRelacionada 
							+ " < " + limites.get(i).limiteSuperior);
				}
				else {
					System.out.print(" | " + limites.get(i).classeRelacionada);
					printLimites.add(limites.get(i).limiteSuperior + " < " + 
													limites.get(i).classeRelacionada);
				}
			}
			System.out.println();
		}
		
		public String qualClasseRepresentada(int ocorrencias) throws Exception{
			
			for(int i = 0; i < limites.size(); ++i) {
				switch(limites.get(i).tipoLimite) {
				case 0:
					if(ocorrencias < limites.get(i).limiteInferior) {
						return limites.get(i).classeRelacionada;
					}
					break;
				case 1:
					if(ocorrencias >= limites.get(i).limiteInferior && 
											ocorrencias <= limites.get(i).limiteSuperior) {
						return limites.get(i).classeRelacionada;
					}
					break;
				case 2:
					if(ocorrencias > limites.get(i).limiteSuperior) {
						return limites.get(i).classeRelacionada;
					}
					break;
				}
			}
			
			throw new Exception("Nenhuma classe foi definida para um valor, erro de projeto");
		}
		
	}
	
	public ResultadoMineracao getResultado() {
		return resultado;
	}
	
}
